1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package org.codehaus.groovy.transform;
20  
21  import groovy.transform.AnnotationCollector;
22  
23  import java.lang.reflect.Method;
24  import java.util.*;
25  
26  import org.codehaus.groovy.GroovyBugError;
27  import org.codehaus.groovy.ast.*;
28  import org.codehaus.groovy.ast.expr.*;
29  import org.codehaus.groovy.ast.stmt.ReturnStatement;
30  import org.codehaus.groovy.ast.stmt.Statement;
31  import org.codehaus.groovy.control.SourceUnit;
32  import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
33  import org.codehaus.groovy.syntax.SyntaxException;
34  
35  import static org.objectweb.asm.Opcodes.*;
36  
37  /**
38   * This class is the base for any annotation alias processor. 
39   * @see AnnotationCollector
40   * @see AnnotationCollectorTransform#visit(AnnotationNode, AnnotationNode, AnnotatedNode, SourceUnit)
41   * @author <a href="mailto:blackdrag@gmx.org">Jochen "blackdrag" Theodorou</a>
42   */
43  public class AnnotationCollectorTransform {
44  
45      private static List<AnnotationNode> getMeta(ClassNode cn) {
46          List<AnnotationNode> meta = cn.getNodeMetaData(AnnotationCollector.class);
47          if (meta == null) {
48              if (cn.isPrimaryClassNode()) {
49                  meta = getTargetListFromAnnotations(cn);
50              } else {
51                  meta = getTargetListFromClass(cn);
52              }
53              cn.setNodeMetaData(AnnotationCollector.class, meta);
54          }
55          return meta;
56      }
57  
58      /**
59       * Class used by {@link org.codehaus.groovy.control.CompilationUnit} to transform the alias class
60       * into what is needed by the compiler. This means removing invalid
61       * modifiers, interfaces and superclasses, as well as adding a static
62       * value method returning our serialized version of the data for processing
63       * from a pre-compiled state. By doing this the old annotations will be
64       * removed as well 
65       * @author <a href="mailto:blackdrag@gmx.org">Jochen "blackdrag" Theodorou</a>
66       */
67      public static class ClassChanger {
68          
69          /**
70           * Method to transform the given ClassNode, if it is annotated with 
71           * {@link AnnotationCollector}. See class description for what the
72           * transformation includes.
73           */
74          public void transformClass(ClassNode cn) {
75              AnnotationNode collector = null;
76              for (ListIterator<AnnotationNode> it = cn.getAnnotations().listIterator(); it.hasNext();) {
77                  AnnotationNode an = it.next();
78                  if (an.getClassNode().getName().equals(AnnotationCollector.class.getName())) {
79                      collector = an;
80                      break;
81                  };
82              }
83              if (collector==null) return;
84              
85              // force final class, remove interface, annotation, enum and abstract modifiers
86              cn.setModifiers((ACC_FINAL+cn.getModifiers()) & ~(ACC_ENUM|ACC_INTERFACE|ACC_ANNOTATION|ACC_ABSTRACT));
87              // force Object super class
88              cn.setSuperClass(ClassHelper.OBJECT_TYPE);
89              // force no interfaces implemented
90              cn.setInterfaces(ClassNode.EMPTY_ARRAY);
91  
92              // add static value():Object[][] method
93              List<AnnotationNode> meta = getMeta(cn); 
94              List<Expression> outer = new ArrayList<Expression>(meta.size());
95              for (AnnotationNode an : meta) {
96                  Expression serialized = serialize(an);
97                  outer.add(serialized);
98              }
99  
100             ArrayExpression ae = new ArrayExpression(ClassHelper.OBJECT_TYPE.makeArray(), outer);
101             Statement code = new ReturnStatement(ae);
102             cn.addMethod(   "value", ACC_PUBLIC+ACC_STATIC,
103                             ClassHelper.OBJECT_TYPE.makeArray().makeArray(), 
104                             Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY,
105                             code);
106 
107             // remove annotations
108             for (ListIterator<AnnotationNode> it = cn.getAnnotations().listIterator(); it.hasNext();) {
109                 AnnotationNode an = it.next();
110                 if (an==collector) continue;
111                 it.remove();
112             }
113         }
114 
115         private Expression serialize(Expression e) {
116             if (e instanceof AnnotationConstantExpression) {
117                 AnnotationConstantExpression ace = (AnnotationConstantExpression) e;
118                 return serialize((AnnotationNode) ace.getValue());
119             } else if (e instanceof ListExpression) {
120                 boolean annotationConstant = false;
121                 ListExpression le = (ListExpression) e;
122                 List<Expression> list = le.getExpressions();
123                 List<Expression> newList = new ArrayList<Expression>(list.size());
124                 for (Expression exp: list) {
125                     annotationConstant = annotationConstant || exp instanceof AnnotationConstantExpression;
126                     newList.add(serialize(exp));
127                 }
128                 ClassNode type = ClassHelper.OBJECT_TYPE;
129                 if (annotationConstant) type = type.makeArray();
130                 return new ArrayExpression(type, newList);
131             }
132             return e;
133         }
134 
135         private Expression serialize(AnnotationNode an) {
136             MapExpression map = new MapExpression();
137             for (String key : an.getMembers().keySet()) {
138                 map.addMapEntryExpression(new ConstantExpression(key), serialize(an.getMember(key)));
139             }
140             List<Expression> l = new ArrayList<Expression>(2);
141             l.add(new ClassExpression(an.getClassNode()));
142             l.add(map);
143             ArrayExpression ae = new ArrayExpression(ClassHelper.OBJECT_TYPE, l);
144             return ae;
145         }
146     }
147     
148     /**
149      * Adds a new syntax error to the source unit and then continues.
150      * 
151      * @param message   the message
152      * @param node      the node for the error report
153      * @param source    the source unit for the error report
154      */
155     protected void addError(String message, ASTNode node, SourceUnit source) {
156         source.getErrorCollector().addErrorAndContinue(new SyntaxErrorMessage(new SyntaxException(
157                 message,  node.getLineNumber(), node.getColumnNumber(), node.getLastLineNumber(), node.getLastColumnNumber()
158                 ), source));
159     }
160 
161     private List<AnnotationNode> getTargetListFromValue(AnnotationNode collector, AnnotationNode aliasAnnotationUsage, SourceUnit source) {
162         Expression memberValue = collector.getMember("value");
163         if (memberValue == null) return Collections.EMPTY_LIST;
164         if (!(memberValue instanceof ListExpression)) {
165             addError("Annotation collector expected a list of classes, but got a "+memberValue.getClass(), collector, source);
166             return Collections.EMPTY_LIST;
167         }
168         ListExpression memberListExp = (ListExpression) memberValue;
169         List<Expression> memberList = memberListExp.getExpressions();
170         if (memberList.size()==0) return Collections.EMPTY_LIST;
171         ArrayList<AnnotationNode> ret = new ArrayList<AnnotationNode>();
172         for (Expression e : memberList) {
173             AnnotationNode toAdd = new AnnotationNode(e.getType());
174             toAdd.setSourcePosition(aliasAnnotationUsage);
175             ret.add(toAdd);
176         }
177         return ret;
178     }
179 
180     private List<AnnotationNode> getStoredTargetList(AnnotationNode aliasAnnotationUsage, SourceUnit source) {
181         ClassNode alias = aliasAnnotationUsage.getClassNode().redirect();
182         List<AnnotationNode> ret = getMeta(alias);
183         return copy(ret, aliasAnnotationUsage);
184     }
185 
186     private List<AnnotationNode> copy(List<AnnotationNode> orig, AnnotationNode aliasAnnotationUsage) {
187         if (orig.isEmpty()) return orig;
188         List<AnnotationNode> ret = new ArrayList<AnnotationNode>(orig.size());
189         for (AnnotationNode an : orig) {
190             AnnotationNode newAn = new AnnotationNode(an.getClassNode());
191             newAn.getMembers().putAll(an.getMembers());
192             newAn.setSourcePosition(aliasAnnotationUsage);
193             ret.add(newAn);
194         }
195         return ret;
196     }
197 
198     private static List<AnnotationNode> getTargetListFromAnnotations(ClassNode alias) {
199         List<AnnotationNode> annotations = alias.getAnnotations();
200         if (annotations.size() < 2) return Collections.EMPTY_LIST;
201         
202         ArrayList<AnnotationNode> ret = new ArrayList<AnnotationNode>(annotations.size());
203         for (AnnotationNode an : annotations) {
204             ClassNode type = an.getClassNode();
205             if (type.getName().equals(AnnotationCollector.class.getName())) continue;
206             AnnotationNode toAdd = new AnnotationNode(type);
207             toAdd.getMembers().putAll(an.getMembers());
208             ret.add(toAdd);
209         }
210         return ret;
211     }
212 
213     private static List<AnnotationNode> getTargetListFromClass(ClassNode alias) {
214         Class<?> c = alias.getTypeClass();
215         Object[][] data;
216         try {
217             Method m = c.getMethod("value");
218             data = (Object[][]) m.invoke(null);
219         } catch (Exception e) {
220             throw new GroovyBugError(e);
221         }
222         return makeListOfAnnotations(data);
223     }
224     
225     private static List<AnnotationNode> makeListOfAnnotations(Object[][] data) {
226         if (data.length==0) return Collections.EMPTY_LIST;
227 
228         ArrayList<AnnotationNode> ret = new ArrayList<AnnotationNode>(data.length);
229         for (Object[] inner : data) {
230             Class anno = (Class) inner[0];
231             AnnotationNode toAdd = new AnnotationNode(ClassHelper.make(anno));
232             ret.add(toAdd);
233 
234             @SuppressWarnings("unchecked")
235             Map<String,Object> member = (Map<String, Object>) inner[1];
236             if (member.size()==0) continue;
237             Map<String, Expression> generated = new HashMap<String, Expression>(member.size());
238             for (String name : member.keySet()) {
239                 Object val = member.get(name);
240                 generated.put(name, makeExpression(val));
241             }
242             toAdd.getMembers().putAll(generated);
243         }
244         return ret;
245     }
246     
247     private static Expression makeExpression(Object o) {
248         if (o instanceof Class) return new ClassExpression(ClassHelper.make((Class) o));
249         //TODO: value as Annotation here!
250         if (o instanceof Object[][]) {
251             List<AnnotationNode> annotations = makeListOfAnnotations((Object[][])o);
252             ListExpression le = new ListExpression();
253             for (AnnotationNode an : annotations) {
254                 le.addExpression(new AnnotationConstantExpression(an));
255             }
256             return le;
257         } else if (o instanceof Object[]) {
258             ListExpression le = new ListExpression();
259             Object[] values = (Object[]) o;
260             for (Object val : values) {
261                 le.addExpression(makeExpression(val));
262             }
263             return le;
264         }
265         return new ConstantExpression(o,true);
266     }
267     
268     /**
269      * Returns a list of AnnotationNodes for the value attribute of the given 
270      * AnnotationNode. 
271      * 
272      * @param collector     the node containing the value member with the list
273      * @param source        the source unit for error reporting
274      * @return              a list of string constants
275      */
276     protected List<AnnotationNode> getTargetAnnotationList(AnnotationNode collector, AnnotationNode aliasAnnotationUsage, SourceUnit source) {
277         List<AnnotationNode> stored     = getStoredTargetList(aliasAnnotationUsage, source);
278         List<AnnotationNode> targetList = getTargetListFromValue(collector, aliasAnnotationUsage, source);
279         int size = targetList.size()+stored.size();
280         if (size==0) return Collections.EMPTY_LIST;
281         ArrayList<AnnotationNode> ret = new ArrayList<AnnotationNode>(size);
282         ret.addAll(stored);
283         ret.addAll(targetList);
284 
285         return ret;
286     }
287 
288     /**
289      * Implementation method of the alias annotation processor. This method will 
290      * get the list of annotations we aliased from the collector and adds it to
291      * aliasAnnotationUsage. The method will also map all members from 
292      * aliasAnnotationUsage to the aliased nodes. Should a member stay unmapped,
293      * we will ad an error. Further processing of those members is done by the
294      * annotations.
295      * 
296      * @param collector                 reference to the annotation with {@link AnnotationCollector}
297      * @param aliasAnnotationUsage      reference to the place of usage of the alias
298      * @param aliasAnnotated            reference to the node that has been annotated by the alias
299      * @param source                    source unit for error reporting
300      * @return list of the new AnnotationNodes
301      */
302     public List<AnnotationNode> visit(AnnotationNode collector, AnnotationNode aliasAnnotationUsage, AnnotatedNode aliasAnnotated, SourceUnit source) {
303         List<AnnotationNode> ret =  getTargetAnnotationList(collector, aliasAnnotationUsage, source);
304         Set<String> unusedNames = new HashSet<String>(aliasAnnotationUsage.getMembers().keySet());
305         
306         for (AnnotationNode an: ret) {
307             for (String name : aliasAnnotationUsage.getMembers().keySet()) {
308                 if (an.getClassNode().hasMethod(name, Parameter.EMPTY_ARRAY)) {
309                     unusedNames.remove(name);
310                     an.setMember(name, aliasAnnotationUsage.getMember(name));
311                 }
312             }
313         }
314 
315         if (unusedNames.size()>0) {
316             String message = "Annotation collector got unmapped names "+unusedNames.toString()+".";
317             addError(message, aliasAnnotationUsage, source);
318         }
319 
320         return ret;
321     }
322 }